[LOJ10172]涂抹果酱

题目

题目描述

Tyvj 两周年庆典要到了,Sam 想为 Tyvj 做一个大蛋糕。蛋糕俯视图是一个 N×M的矩形,它被划分成 N×M个边长为 1×1的小正方形区域(可以把蛋糕当成 N 行 M 列的矩阵)。蛋糕很快做好了,但光秃秃的蛋糕肯定不好看!所以,Sam 要在蛋糕的上表面涂抹果酱。果酱有三种,分别是红果酱、绿果酱、蓝果酱,三种果酱的编号分别为 1,2,3。为了保证蛋糕的视觉效果,Admin 下达了死命令:相邻的区域严禁使用同种果酱。但 Sam 在接到这条命令之前,已经涂好了蛋糕第 K 行的果酱,且无法修改。
现在 Sam 想知道:能令 Admin 满意的涂果酱方案有多少种。请输出方案数 $ mod 10^6$
若不存在满足条件的方案,请输出 0。

输入格式

输入共三行。
第一行:N,M;
第二行:K;
第三行:M 个整数,表示第 K 行的方案。
字母的详细含义见题目描述,其他参见样例。

输出格式

输出仅一行,为可行的方案总数。

样例

输入样例

1
2
3
2 2 
1
2 3

输出样例

1
3

样例说明

数据范围和提示

对于 30% 的数据,1≤N×M≤20;
对于 60% 的数据,1≤N≤1000,1≤M≤3;
对于 100% 的数据,1≤N≤10000,1≤M≤5。

题解

一开始拿到这道题目发现不就是状态压缩嘛,而且是相邻不重复的计数问题,这种题目早就已经轻车熟路了。
但是仔细看了才发现不简单,不是普通的二进制状压,而是三进制状压。

但是进制本质上都是一样的,我们一边参照二进制状压,一边实现三进制状压。

首先是左移右移,实际上二进制的左移右移就是$\times 2$还有/2,既然是三进制,当然是*3还有/3

还有就是实现状态合法判定,具体的在check函数和judge函数的注释里,这两个函数稍微有点不同。

状态转移的话就是上一行的所有合法状态之和(这个和二进制状压是一样的)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<iostream>
#include<cstdio>
using namespace std;
const int MAXN=10004,mod=1000000;
int n,m,k,pos;
int ban,ans=0;
int can[1005],tot=0;
int f[MAXN][1005];

inline bool check(int x){//自身合法性判断,每次弹出最末尾的一个数,存起来,看看和下一个数是不是一样
int tmp=0x3f;
for(int i=1;i<=m;++i){
if(tmp==x%3)return false;
tmp=x%3,x/=3;
}
return true;
}

inline bool judge(int a,int b){//相互合法性判断,就是按位比对
for(int i=1;i<=m;++i){
if(a%3==b%3)return false;
a/=3,b/=3;
}
return true;
}

int main(){
cin>>n>>m>>k;
int in=1;
for(int i=0;i<m;i++)in*=3;
for(int i=0;i<in;++i)if(check(i))can[++tot]=i;
for(int i=1;i<=m;++i){
int tmp;
scanf("%d",&tmp);
ban=ban*3+tmp-1;
}
for(int i=1;i<=tot;++i)if(ban==can[i]){pos=i;break;}
if(!pos){cout<<0;return 0;}
for(int i=1;i<=n;++i){
if(i==k){
if(i==1)f[i][pos]=1;
else for(int j=1;j<=tot;++j)if(judge(can[pos],can[j]))(f[i][pos]+=f[i-1][j])%=mod;
}
else for(int j=1;j<=tot;++j){
if(i==1)f[i][j]=1;
else for(int k=1;k<=tot;++k)if(judge(can[j],can[k]))(f[i][j]+=f[i-1][k])%=mod;
}
}
for(int i=1;i<=tot;++i)(ans+=f[n][i])%=mod;
cout<<ans;
return 0;
}